#include "memory.h"
#include "bitmap.h"
#include "print.h"
#include "debug.h"
#include "global.h"

#define PAGE_SIZE 4096 //页的大小，4096字节，即4K。
#define MEM_BITMAP_BASE 0xc009a000
#define K_HEAP_START 0xc0100000  //内核可用的堆内存，虚拟地址的起始地址。（3G之后的1MB开始，最终映射的依旧是 实际内存的1M开始位置）
#define MEM_SIZE 0x903 //详见： boot/loader.s中的 total_mem_size



#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)

/**
 * 物理内存池
 * 由于物理内存只会在 memory.c文件中使用到。因此，没有放在头文件中。
*/
struct pool {
    struct bitmap pool_bitmap; // 用于管理物理内存的 位图
    uint32_t phy_addr_start;  // 物理内存池所管理的物理内存的起始物理地址
    uint32_t pool_size;  //物理内存池的大小，字节数
};


struct pool kernel_pool,user_pool; //将内核、用户的物理内存分开管理，以保证内核物理内存的独立性。

struct virtual_addr kernel_addr; //这里只定义了用于管理内核虚拟地址的结构体


static uint32_t* getPde(uint32_t vaddr);
static uint32_t* getPte(uint32_t vaddr);
static void mem_pool_init(uint32_t phyMemSize);


static void* malloc_phy_page(struct pool* p);
static void* malloc_v_page(struct virtual_addr  * va, uint32_t pageCnt);
static void buildOnePage(void* vaddr,void* paddr);


void mem_init(void)
{
    uint32_t phyMemSize = *((uint32_t*)MEM_SIZE);

    put_str("memory init, mem size: 0x");
    put_int(phyMemSize);
    put_str(" bit (0x");
    put_int(phyMemSize/1024/1024);
    put_str(" MB)");
    put_str("\n");

    mem_pool_init(phyMemSize);

    put_str("memory init done\n");
}

void* malloc_kernel_page( uint32_t pcnt)
{
    return malloc_page(PT_KERNEL,pcnt);
}
void* malloc_page(enum pool_type pt, uint32_t pcnt)
{   
    void* vaddr = NULL;
    if(pt == PT_KERNEL){
        vaddr = malloc_v_page(&kernel_addr,pcnt);
        uint32_t vaddrInt = (uint32_t)vaddr;
        uint32_t i = 0;
        for(i = 0 ; i < pcnt; i++){
            void* paddr = malloc_phy_page(&kernel_pool);
            ASSERT(paddr != NULL);
            buildOnePage((void *)vaddrInt,paddr);
            vaddrInt += PAGE_SIZE;
        }
        
    }else{
        ASSERT( pt != PT_USER);
    }
    return vaddr;
}
static void* malloc_phy_page(struct pool* p)
{
    int idx = bitmap_scan(&p->pool_bitmap,1);
    
    if(idx != -1){
        bitmap_set(&p->pool_bitmap,idx,1);
        return (void*)(p->phy_addr_start + idx * PAGE_SIZE);
    }else{
        return NULL;
    }
    
}

static void* malloc_v_page(struct virtual_addr  * va, uint32_t pageCnt)
{
    int idx = bitmap_scan(&va->vaddr_bitmap,pageCnt);
    if(idx == -1){
        return NULL;
    }else{
        uint32_t i = 0;

        while(i < pageCnt){
            bitmap_set(&va->vaddr_bitmap,idx + i , 1);
            i++;
        }
        return (void*)(va->vaddr_start + idx * PAGE_SIZE);
    }
}

static void buildOnePage(void* vaddr,void* paddr)
{
    uint32_t vaddrInt = (uint32_t) vaddr;
    uint32_t paddrInt = (uint32_t) paddr;

    uint32_t* pde = getPde(vaddr);
    uint32_t* pte = getPte(vaddr);
    if( (*pde) & PG_P_1){

        if( !(*pte) & PG_P_1){
            *pte = (uint32_t) paddr |  PG_US_U | PG_RW_W | PG_P_1;
        }else{
            PANIC("ptd dulplacate!");
            *pte = (uint32_t) paddr |  PG_US_U | PG_RW_W | PG_P_1;
        }

    }else{
        void* ptePhyAddr = malloc_phy_page(&kernel_pool);
        *pde = (uint32_t) ptePhyAddr |  PG_US_U | PG_RW_W | PG_P_1;

        // Clear PTE
        memset((void*)((int)pte & 0xfffff000), 0, PAGE_SIZE);
        *pte = (uint32_t) paddr |  PG_US_U | PG_RW_W | PG_P_1;
    }

}
static void mem_pool_init(uint32_t phyMemSize)
{
    uint32_t pageTableSize = 256 * PAGE_SIZE ;// 参考 boot/loader.s中的代码，已经在内存中安排了 256个页 255个PTE 和 1个PDE
    uint32_t usedMemBits = pageTableSize + 0x100000;  // 即 256个页 +  内核预留的1M内存空间

    uint32_t totalFreeMemBits = phyMemSize - usedMemBits;

    /**
     * 计算可用内存大小，内核与用户平分剩余可以内存容量
    */
    uint32_t kpoolSize = totalFreeMemBits  / 2;
    uint32_t upoolSize = totalFreeMemBits - kpoolSize;


    uint32_t kPhyAddrStart = usedMemBits; //内核内存从剩余可用空间处开始
    uint32_t uPhyAddrStart = usedMemBits + kpoolSize; //用户内存紧接着内核可用空间的末尾开始。


   
    kernel_pool.phy_addr_start = kPhyAddrStart;
    kernel_pool.pool_size = kpoolSize;
    kernel_pool.pool_bitmap.bits = (void *) MEM_BITMAP_BASE;
    kernel_pool.pool_bitmap.btmp_bytes_len = kernel_pool.pool_size / PAGE_SIZE / 8;

    user_pool.phy_addr_start = uPhyAddrStart;
    user_pool.pool_size = upoolSize;
    user_pool.pool_bitmap.bits = (void *) (MEM_BITMAP_BASE +  kernel_pool.pool_size / PAGE_SIZE);
    user_pool.pool_bitmap.btmp_bytes_len = user_pool.pool_size / PAGE_SIZE / 8;

    bitmap_init(&kernel_pool.pool_bitmap);
    bitmap_init(&user_pool.pool_bitmap);

    put_str("  kernel_pool_bitmap_start:0x");put_int((int)kernel_pool.pool_bitmap.bits);
    put_str("; kernel_pool_phy_addr_start:0x");put_int(kernel_pool.phy_addr_start);
    put_str("\n");
    put_str("  user_pool_bitmap_start:0x");put_int((int)user_pool.pool_bitmap.bits);
    put_str("; user_pool_phy_addr_start:0x");put_int(user_pool.phy_addr_start);
    put_str("\n");

    //开始初始化内核虚拟内存管理结构体
    kernel_addr.vaddr_start = K_HEAP_START;
    kernel_addr.vaddr_bitmap.bits =  (void *) (MEM_BITMAP_BASE +  kernel_pool.pool_size  / PAGE_SIZE +  user_pool.pool_size  / PAGE_SIZE);

    // 按实际可用的物理内存大小，初始化位图大小
    kernel_addr.vaddr_bitmap.btmp_bytes_len = totalFreeMemBits / PAGE_SIZE / 8 ;
    bitmap_init(&kernel_addr.vaddr_bitmap);

     put_str("  kernel_addr_bitmap_start:0x");put_int((int)kernel_addr.vaddr_bitmap.bits);
    put_str("; kernel_vaddr_addr_start:0x");put_int( kernel_addr.vaddr_start);
    put_str("\n");
}




static uint32_t* getPde(uint32_t vaddr)
{
    /**
     * 由于在 初始化页目录表的时候，最后一项存放的地址是页目录表的起始地址。
     * 所有，0xfffff000 这个虚拟地址可以获取到页目录表的基地址：
     * 前二十位全为1：硬件解析后，获取获取最后一个页目录项，最有一个页目录项获取到的还是页目录的初始地址
     * 中间二十位全为1：硬件解析后，获取到的是依旧是 页目录的最后一项
     * PDE_IDX * 4 为 目标 页目录项的偏移地址。
     * 通过上述技巧，获取到了 虚拟地址所对应的页目录项
    */
    return  (uint32_t*)(0xfffff000 + PDE_IDX(vaddr) * 4);
}

static uint32_t* getPte(uint32_t vaddr)
{
    /**
     * 由于在 初始化页目录表的时候，最后一项存放的地址是页目录表的起始地址。
     * 所有，0xfffff000 这个虚拟地址可以获取到页目录表的基地址：
     * 前二十位全为1：硬件解析后，获取获取最后一个页目录项，最有一个页目录项获取到的还是页目录的初始地址
     * 中间二十位全为实际页目录项的索引：硬件解析后，获取到的是  正确的页目录项
     * PTE_IDX * 4 为 目标 页表项的偏移地址。
     * 通过上述技巧，获取到了 虚拟地址所对应的页表录项
    */
    return  (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
}

